/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.bindings;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.balisunrise.annotation.util.ReflectUtilities;
import org.balisunrise.application.Application;
import org.balisunrise.common.BindingException;

/**
 *
 * @author Glauco
 */
public class BindingPropertyListener implements PropertyChangeListener,
        BindingTrigger{

    /**
     * Utilizado para disparar o metodo de atualizacao do source do objeto
     * Binding.
     * @see Binding#sourcePropertyChanged()
     */
    public static final int SOURCE = 1;

    /**
     * Utilizado para disparar o metodo de atualizacao do target do objeto
     * Binding.
     * @see Binding#targerPropertyChanged()
     */
    public static final int TARGET = 2;
    
    /**
     * Propriedade pai.
     */
    private BindingPropertyListener parent;

    /**
     * Objeto source.
     */
    private Object source;

    /**
     * Classe do source.
     */
    private Class sourceClass;

    /**
     * Método para adicionar o PropertyChangeListener.
     */
    private Method sourceAddPropMethod;

    /**
     * Método para remover o PropertyChangeListener.
     */
    private Method sourceRemPropMethod;
    
    /**
     * Mapa de escutadores de propriedades filhas.<br>
     * A chave do mapa é uma propriedade do Source.
     * Estas propriedades filhas escutam a partir de um source que é um
     * objeto de uma propriedade do source pai.
     */
    private Map<String,BPLChild> children;

    /**
     * Lista dos Bindings.
     */
    private List<BindingProperty> bindings;

    /**
     * Indica se esta escutando as alterações de propriedades do source.
     */
    private boolean registered;

    public BindingPropertyListener(BindingPropertyListener parent,
            Object source) throws BindingException {

        if (source == null && parent == null)
            throw new BindingException(
                    "Exception creating BindingPropertyListener: " +
                    "The source and the parent are null!");

        this.parent = parent;
        this.source = source;
        
        if(source != null){
            this.sourceClass = source.getClass();
            init();
        }
        
        children = new HashMap<String, BPLChild>();
        bindings = new ArrayList<BindingProperty>();
        registered = false;
    }

    public void addBinding(String property, Binding binding, int fire){
        
        String[] props;

        if(property.contains("."))
            props = property.split("\\.");
        else
            props = new String[]{property};

        String prop = props[0];
        BindingProperty bp = new BindingProperty(prop, binding, fire);
        bindings.add(bp);

        // cria child
        if(props.length > 1){
            // propriedade do filho, removendo a primeira propriedade
            StringBuilder sb = new StringBuilder();
            for (int i = 1; i < props.length; i++) {
                if(sb.length() > 0)
                    sb.append('.');
                sb.append(props[i]);
            }
            try{
                // filho
                BPLChild child;
                // busca filho, ou cria se não existir
                if(children.containsKey(prop))
                    child = children.get(prop);
                else{
                    Object ps = null;
                    if (source != null)
                        ps = BeansPropertyReader.getProperty(source, prop);
                    child = new BPLChild(this,ps);
                    children.put(prop, child);
                }
                // adiciona o binding no filho
                child.addBinding(sb.toString(), binding, fire);

            }catch(Exception ex){
                Application.get().getLogger().log(Level.INFO,
                        "Problem adding Binding.", ex);
            }
        }
    }

    public void removeBinding(Binding binding){
        for (BindingProperty bp : bindings) {
            if(bp.getBinding() == binding)
                bindings.remove(bp);
        }
        for (BPLChild child : children.values()) {
            child.removeBinding(binding);
        }
    }

    @Override
    public void activate(){

        if(registered)
            return ;

        try{
            sourceAddPropMethod.invoke(source, this);
            registered = true;
            for (String prop : children.keySet()) {
                Object ps = BeansPropertyReader.getProperty(source, prop);
                children.get(prop).register(ps);
            }
        }catch(Exception ex){
            Application.get().getLogger().log(Level.INFO,
                    "Problem activating bindings.", ex);

            for (BPLChild child : children.values()) {
                child.unRegister();
            }
        }
    }

    @Override
    public void deactivate(){

        if(!registered)
            return ;

        try{
            sourceRemPropMethod.invoke(source, this);
            registered = false;
        }catch(Exception ex){
            Application.get().getLogger().log(Level.INFO,
                    "Problem deactivating bindings", ex);
        }
        for (BPLChild child : children.values()) {
            child.unRegister();
        }
    }

    protected void setSource(Object source) throws BindingException{

        boolean oldRegistered = registered;

        if (this.source != null)
            deactivate();

        this.source = source;
        if (source == null)
            return ;

        if (sourceClass == null || !sourceClass.equals(source.getClass())){
            sourceClass = source.getClass();
            init();
        }

        if(oldRegistered)
            activate();
    }

    /**
     * Escuta de alteração de propriedade.
     * @param evt Evento de propriedade alterada.
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {

        Object newValue = evt.getNewValue();
        String prop = evt.getPropertyName();

        if(children.containsKey(prop)){
            try{
                children.get(prop).register(newValue);
            }catch(Exception ex){
                Application.get().getLogger().log(Level.INFO,
                        "Problem whit binding when property changed", ex);
            }
        }
        for (BindingProperty bp : bindings) {
            if(bp.getProperty().equals(prop))
                bp.propertyChanged();
        }
    }

    protected final void init() throws BindingException{

        ReflectUtilities r = new ReflectUtilities(sourceClass);

        sourceAddPropMethod = r.getMethod("addPropertyChangeListener",
                PropertyChangeListener.class);
        sourceRemPropMethod = r.getMethod("removePropertyChangeListener",
                PropertyChangeListener.class);

        if(sourceAddPropMethod == null){
            throw new BindingException(
                    "Method \"addPropertyChangeListener\"not found: class[" +
                    sourceClass.getName() + "]");
        }
        if(sourceRemPropMethod == null){
            throw new BindingException(
                    "Method \"removePropertyChangeListener\"not found: class[" +
                    sourceClass.getName() + "]");
        }

    }
}
